
# coding: utf-8

# In[ ]:


#预测用户uid可能播放某一首歌的次数
from __future__ import division

import pandas as pd
import numpy as np
   
  
class  User_based_CF:
    
    def __init__(self, X):  
       self.X = X  #评分表
       self.mu = np.mean(self.X[:,3])  #先计算出平均打分的值

       self.SongsForUser={}   #用户听过的所有歌曲
       self.UsersForSong={}   #听过某一首歌的所有用户
       
       for i in range(self.X.shape[0]):  
           uid=self.X[i][0]  #user id
           song_id=self.X[i][1] #song_id 
           listen_count=self.X[i][2]  #listen_count
           
           self.UsersForSong.setdefault(song_id,{})  
           self.SongsForUser.setdefault(uid,{}) 
           
           self.UsersForSong[song_id][uid]=listen_count  
           self.SongsForUser[uid][song_id]=listen_count
           
           #self.similarity.setdefault(i_id,{}) 
           
       pass  
   
       n_Users = len(self.SongsForUser)+1  #数组的索引从0开始，浪费第0个元素
       print(n_Users-1) 
       self.similarity = np.zeros((n_Users, n_Users), dtype=np.float)
       self.similarity[:,:] = -1
          
   
   #计算user uid1和uid2之间的相似性
    def sim_cal(self, uid1, uid2):
       if self.similarity[uid1][uid2]!=-1:  #如果已经计算好
           return self.similarity[uid1][uid2]  
       
       si={}  
       for song in self.SongsForUser[uid1]:  
           if song in self.SongsForUser[uid2]:  
               si[song]=1  
       
       #print si
       n=len(si)  
       if (n==0):  
           self.similarity[uid1][uid2]=0  
           self.similarity[uid1][uid2]=0  
           return 0  
       
       
       s1=np.array([self.SongsForUser[uid1][song] for song in si])  
       
       
       s2=np.array([self.SongsForUser[uid2][song] for song in si])  
       
       sum1=np.sum(s1)  
       sum2=np.sum(s2)  
       sum1Sq=np.sum(s1**2)  
       sum2Sq=np.sum(s2**2)  
       pSum=np.sum(s1*s2)  
       
       #分子
       num=pSum-(sum1*sum2/n)  
       
       #分母
       den=np.sqrt((sum1Sq-sum1**2/n)*(sum2Sq-sum2**2/n))  
       if den==0:  
           self.similarity[uid1][uid2]=0  
           self.similarity[uid2][uid1]=0  
           return 0  
       
       self.similarity[uid1][uid2]=num/den  
       self.similarity[uid2][uid1]=num/den  
       return num/den  
           
   #预测用户uid对Item i_id的打分
    def pred(self,uid,song_id):  
       sim_accumulate=0.0  
       listen_count=0.0  
           
       for user in self.UsersForSong[song_id]:
           sim = self.sim_cal(user,uid)   
           if sim<=0:continue  
          
           
           listen_count += sim * self.UsersForSong[song_id][user] 
           sim_accumulate += sim  
       
       # print(listen_count,sim_accumulate  ) 
       if sim_accumulate==0: #no same user rated,return average rates of the data  
           return  self.mu  
       return listen_count/sim_accumulate  

# 测试模型的准确率和召回率
    def test(self,test_X):  
       test_X=np.array(test_X) 
       output=[]  
       sums=0  
       print("the test data size is ",test_X.shape)  
       
       for i in range(test_X.shape[0]):  
           #print i
           uid = test_X[i][0]  #user id
           song_id = test_X[i][1] #song_id 
       
           #设置默认值，否则用户或item没在训练集中出现时会报错
           self.UsersForSong.setdefault(song_id,{})  
           self.SongsForUser.setdefault(uid,{})
           
           pre=self.pred(uid, song_id)  
           output.append(pre)  
           #print pre,test_X[i][2]  
           sums += (pre-test_X[i][2])**2  
       rmse=np.sqrt(sums/test_X.shape[0])  
       print( "the rmse on test data is ",rmse)  
       return output  

